/*
 *  Copyright (C) 2010 Glauco
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.balisunrise.annotation.util;

import java.lang.reflect.Field;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Properties;
import org.balisunrise.annotation.CriteriaDefinition;
import org.balisunrise.annotation.CriteriaEmbedded;
import org.balisunrise.annotation.CriteriaSelection;
import org.balisunrise.annotation.FilterCriteria;
import org.balisunrise.annotation.FilterType;
import org.balisunrise.annotation.Selection;
import org.balisunrise.application.Application;
import org.balisunrise.common.PropertiesReader;

/**
 *
 * @author glacuo
 */
public class CriteriaUtilities {

    private static final String MESSAGE = "Erro ao ler Annotation \"FilterCriteria\".";
    public static final int DEFINITION = 1;
    public static final int EMBEDDED = 2;
    public static final int SELECTION = 3;

    /**
     * Atributo com a anotação <code>@FilterCriteria</code> da classe de uma entidade.
     */
    public Field field;

    /**
     * Alias (nome) da definição.
     */
    public String alias;

    /**
     * Indice em que o atributo deve aparecer na tela.
     */
    public int index;

    /**
     * Define o tipo de filtro que será utilizado em uma pesquisa.
     */
    public FilterType filterType;

    /**
     * String do label que aparecerá na frente do componente que edita um
     * atributo de uma entidade.
     */
    public String label;

    /**
     * Esta definição é utilizada no filtro para ignora-lo se o valor for nulo.
     */
    public boolean emptyForAll;

    /**
     * Alias do atributo que será usada para construir os campo de um filtro
     * de uma entidade associada encapsulada.
     */
    public String call;

    /**
     * alias da tela de pesquisa do atributo de seleção.
     */
    public String search;

    /**
     * Mensagem para quando uma entidade de seleção não foi encontrada.
     */
    public String notFoundMessage;

    /**
     * Tipo da anotação de <code>@PanelField</code>.
     * @see CriteriaUtilities#DEFINITION
     * @see CriteriaUtilities#EMBEDDED
     * @see CriteriaUtilities#SELECTION
     */
    public int type;

    /**
     * Alinhamento.
     */
    public int aligment;
    
    /**
     * Propriedades do campo da entidade.
     */
    public Properties properties;

    /**
     * Determina se este campo é utilizado para seleção de entidades.
     */
    public boolean selection;

    /**
     * Campos encapsulados ou de seleção.
     */
    public List<CriteriaUtilities> embedded;

    /**
     *
     * @param field
     * @param alias
     * @return
     * @throws AnnotationsUtilitiesException
     */
    public static CriteriaUtilities create(Field field, String alias)
            throws AnnotationsUtilitiesException{

        if (field == null)
            throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nParâmentro \"field\" não pode ser nulo!");

        if(alias == null)
            throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nParâmentro \"field\" não pode ser nulo!");

        // Le anotação FilterCriteria.
        FilterCriteria fc = readFilterCriteriaAnnotation(field);

        CriteriaUtilities fcu = new CriteriaUtilities();
        fcu.field = field;
        fcu.alias = alias;
        fcu.selection = isSelection(field, alias);

        PropertiesReader pr = Application.get().getPropertiesReader();
        Properties p = new Properties();
        if (pr != null)
            p.putAll(pr.readFieldProperties(field));

        try{
            CriteriaDefinition cd = readCriteriaDefinitionAnnotation(fc, alias);
            
            fcu.label = cd.label();
            fcu.filterType = cd.filterType();
            fcu.index = cd.index();
            fcu.emptyForAll = cd.emptyForAll();
            fcu.aligment = cd.alignment();
            fcu.type = DEFINITION;
            if (pr != null)
                p.putAll(pr.readCriteriaDefinitionProperties(cd));
            fcu.properties = p;
            return fcu;
        }catch(AnnotationsUtilitiesException aue){
            // não trata nada aqui pois a exceção é disparada no final
        }

        try{
            CriteriaEmbedded ce = readCriteriaEmbeddedAnnotation(fc, alias);
            fcu.index = ce.index();
            fcu.call = ce.filter();
            fcu.type = EMBEDDED;
            if (pr != null)
                p.putAll(pr.readCriteriaEmbeddedProperties(ce));
            fcu.properties = p;
            return fcu;
        }catch(AnnotationsUtilitiesException aue){
            // não trata nada aqui pois a exceção é disparada no final
        }

        try{
            CriteriaSelection cs = readCriteriaSelectionAnnotation(fc, alias);
            fcu.index = cs.index();
            fcu.call = cs.filter();
            fcu.search = cs.search();
            fcu.notFoundMessage = cs.notFoundMessage();
            fcu.type = SELECTION;
            if (pr != null)
                p.putAll(pr.readCriteriaSelectionProperties(cs));
            fcu.properties = p;
            return fcu;
        }catch(AnnotationsUtilitiesException aue){
            // não trata nada aqui pois a exceção é disparada no final
        }

        throw new AnnotationsUtilitiesException(
                "Erro ao criar \"FilterCriteriaUtilities\"!" +
                "\nAnnotation \"CriteriaDefinition\" \"CriteriaEmbedded\" " +
                "\"CriteriaSelection\" não encontrada para o nome\"" +
                alias +"\"!");
    }

    /**
     *
     * @param field
     * @return
     * @throws AnnotationsUtilitiesException
     */
    public static FilterCriteria readFilterCriteriaAnnotation(Field field)
            throws AnnotationsUtilitiesException{

        if (field == null)
            throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nParâmentro \"field\" não pode ser nulo!");

        if(!field.isAnnotationPresent(FilterCriteria.class))
            throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nAnnotation \"FilterCriteria\" não encontrada!");

        return field.getAnnotation(FilterCriteria.class);
    }

    /**
     *
     * @param filter
     * @param alias
     * @return
     * @throws AnnotationsUtilitiesException
     */
    public static CriteriaDefinition readCriteriaDefinitionAnnotation(
            FilterCriteria filter, String alias)
            throws AnnotationsUtilitiesException{

        if (filter == null)
            throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nParâmentro \"filter\" não pode ser nulo!");

        if (alias == null)
            throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nParâmentro \"id\" não pode ser nulo!");

        for (int i = 0; i < filter.definitions().length; i++) {
            CriteriaDefinition cd = filter.definitions()[i];
            for (int j = 0; j < cd.alias().length; j++) {
                if(cd.alias()[j].equals(alias))
                    return cd;
            }
        }

        throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nAnnotation \"CriteriaDefinition\" não encontrada para o "
                    + "nome\""+ alias +"\"!");
    }

    /**
     *
     * @param filter
     * @param alias
     * @return
     * @throws AnnotationsUtilitiesException
     */
    public static CriteriaEmbedded readCriteriaEmbeddedAnnotation(
            FilterCriteria filter, String alias)
            throws AnnotationsUtilitiesException{

        if (filter == null)
            throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nParâmentro \"filter\" não pode ser nulo!");

        if (alias == null)
            throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nParâmentro \"alias\" não pode ser nulo!");

        for (int i = 0; i < filter.embedded().length; i++) {
            CriteriaEmbedded ce = filter.embedded()[i];
            for (int j = 0; j < ce.alias().length; j++) {
                if (ce.alias()[j].equals(alias))
                    return ce;
            }
        }

        throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nAnnotation \"CriteriaEmbedded\" não encontrada para o "
                    + "nome\""+ alias +"\"!");
    }

    /**
     *
     * @param filter
     * @param alias
     * @return
     * @throws AnnotationsUtilitiesException
     */
    public static CriteriaSelection readCriteriaSelectionAnnotation(
            FilterCriteria filter, String alias)
            throws AnnotationsUtilitiesException{

        if (filter == null)
            throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nParâmentro \"filter\" não pode ser nulo!");

        if (alias == null)
            throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nParâmentro \"alias\" não pode ser nulo!");

        for (int i = 0; i < filter.embedded().length; i++) {
            CriteriaSelection cs = filter.selection()[i];
            for (int j = 0; j < cs.alias().length; j++) {
                if (cs.alias()[j].equals(alias))
                    return cs;
            }
        }

        throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nAnnotation \"CriteriaEmbedded\" não encontrada para o "
                    + "nome\""+ alias +"\"!");
    }

    /**
     *
     * @param list
     * @throws AnnotationsUtilitiesException
     */
    public static void sortFilterFieldsList(List<CriteriaUtilities> list)
            throws AnnotationsUtilitiesException{

        if (list == null)
            throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nParâmentro \"list\" não pode ser nulo!");

        //Ordena a lista
        Collections.sort(list, new Comparator<CriteriaUtilities>(){
            @Override
            public int compare(CriteriaUtilities o1,
                    CriteriaUtilities o2) {

                int order1 = o1.index;
                int order2 = o2.index;

                // compara
                return order1 < order2 ? -1 : (order1 > order2 ? 1 : 0);

            }
        });
    }

    /**
     * 
     * @param field
     * @param alias
     * @return
     * @throws AnnotationsUtilitiesException
     */
    public static boolean isSelection(Field field, String alias)
            throws AnnotationsUtilitiesException{

        if (field == null)
            throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nParâmentro \"field\" não pode ser nulo!");

        if (alias == null)
            throw new AnnotationsUtilitiesException( MESSAGE +
                    "\nParâmentro \"alias\" não pode ser nulo!");

        if (field.isAnnotationPresent(Selection.class)){
            Selection selection = field.getAnnotation(Selection.class);
            for (String s : selection.aliasForCriteria()) {
                if(s.equals(alias))
                    return true;
            }
        }

        return false;
    }

    /**
     * Conta o número de colunas.
     * No caso do tipo ser "DEFINITION" o resultado será sempre um (1),
     * caso contrario dependera dos itens da lista "embedded".
     * @return O número de itens.
     */
    public int count(){
        if(type == DEFINITION)
            return 1;

        int count = 0;
        for (CriteriaUtilities fcu : embedded) {
            count += fcu.count();
        }
        return count;
    }
}
